import 'dart:ui';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_graphics_view/src/context/scene_model_context.dart';
import 'package:flutter_graphics_view/src/context/scene_state_context.dart';
import 'package:flutter_graphics_view/src/model/item_model_data.dart';
import 'package:flutter_graphics_view/src/policy/base/policy_set.dart';
import 'package:flutter_graphics_view/src/policy/defaults/scene_control_policy.dart';
import 'package:flutter_graphics_view/src/widget/graphics_item_widget.dart';
import 'package:provider/provider.dart';

class GraphicsViewWidget extends StatefulWidget {
  final PolicySet policy;

  const GraphicsViewWidget({
    Key? key,
    required this.policy,
  }) : super(key: key);

  @override
  _GraphicsViewWidgetState createState() {
    return _GraphicsViewWidgetState();
  }
}

class _GraphicsViewWidgetState extends State<GraphicsViewWidget>
    with TickerProviderStateMixin {
  PolicySet? withControlPolicy;

  @override
  void initState() {
    withControlPolicy = (widget.policy is SceneControlPolicy ||
            widget.policy is SceneMovePolicy)
        ? widget.policy
        : null;

    (withControlPolicy as SceneControlPolicy?)?.setAnimationController(
      AnimationController(
        vsync: this,
        duration: const Duration(seconds: 1),
      ),
    );

    super.initState();
  }

  @override
  void dispose() {
    (withControlPolicy as SceneControlPolicy?)?.disposeAnimationController();
    super.dispose();
  }

  List<Widget> showItems(SceneModelContext sceneModelContext) {
    var zOrderedItems = sceneModelContext.models.values.toList();
    zOrderedItems.sort((a, b) => a.zOrder.compareTo(b.zOrder));
    return zOrderedItems
        .map((itemModelData) => ChangeNotifierProvider<ItemModelData>.value(
              value: itemModelData,
              child: GraphicsItem(policy: widget.policy),
            ))
        .toList();
  }

  List<Widget> showOtherWithItemModelDataUnder(
      SceneModelContext sceneModelContext) {
    return sceneModelContext.models.values.map((ItemModelData itemModelData) {
      return ChangeNotifierProvider<ItemModelData>.value(
        value: itemModelData,
        builder: (context, child) {
          return Consumer<ItemModelData>(
            builder: (context, data, child) {
              return widget.policy
                  .showCustomWidgetWithItemModelDataUnder(context, data);
            },
          );
        },
      );
    }).toList();
  }

  List<Widget> showOtherWithItemModelDataOver(
      SceneModelContext sceneModelContext) {
    return sceneModelContext.models.values.map((ItemModelData itemModelData) {
      return ChangeNotifierProvider<ItemModelData>.value(
        value: itemModelData,
        builder: (context, child) {
          return Consumer<ItemModelData>(
            builder: (context, data, child) {
              return widget.policy
                  .showCustomWidgetWithItemModelDataOver(context, data);
            },
          );
        },
      );
    }).toList();
  }

  List<Widget> showBackgroundWidgets() {
    return widget.policy.showCustomWidgetsOnSceneBackground(context);
  }

  List<Widget> showForegroundWidgets() {
    return widget.policy.showCustomWidgetsOnSceneForeground(context);
  }

  Widget viewStack(SceneModelContext sceneModelContext) {
    return Stack(
      clipBehavior: Clip.none,
      fit: StackFit.expand,
      children: [
        ...showBackgroundWidgets(),
        ...showOtherWithItemModelDataUnder(sceneModelContext),
        ...showItems(sceneModelContext),
        ...showOtherWithItemModelDataOver(sceneModelContext),
        ...showForegroundWidgets(),
      ],
    );
  }

  Widget viewAnimated(SceneModelContext sceneModelContext) {
    return AnimatedBuilder(
      animation:
          (withControlPolicy as SceneControlPolicy).getAnimationController(),
      builder: (BuildContext context, Widget? child) {
        (withControlPolicy as SceneControlPolicy).canUpdateSceneModel = true;
        return Transform(
          transform: Matrix4.identity()
            ..translate(
                (withControlPolicy as SceneControlPolicy).transformPosition.dx,
                (withControlPolicy as SceneControlPolicy).transformPosition.dy)
            ..scale((withControlPolicy as SceneControlPolicy).transformScale),
          child: child,
        );
      },
      child: viewStack(sceneModelContext),
    );
  }

  @override
  Widget build(BuildContext context) {
    final sceneModelContext = Provider.of<SceneModelContext>(context);
    final sceneStateContext = Provider.of<SceneStateContext>(context);
    return RepaintBoundary(
      key: sceneStateContext.sceneGlobalKey,
      child: AbsorbPointer(
        absorbing: !sceneStateContext.interactive,
        child: Listener(
          onPointerSignal: (PointerSignalEvent event) =>
              widget.policy.onScenePointerSignal(event),
          child: GestureDetector(
            child: Container(
              color: sceneStateContext.color,
              child: ClipRect(
                child: (withControlPolicy != null)
                    ? viewAnimated(sceneModelContext)
                    : viewStack(sceneModelContext),
              ),
            ),
            onScaleStart: (details) => widget.policy.onSceneScaleStart(details),
            onScaleUpdate: (details) =>
                widget.policy.onSceneScaleUpdate(details),
            onScaleEnd: (details) => widget.policy.onSceneScaleEnd(details),
            onTap: () => widget.policy.onSceneTap(),
            onTapDown: (TapDownDetails details) =>
                widget.policy.onSceneTapDown(details),
            onTapUp: (TapUpDetails details) =>
                widget.policy.onSceneTapUp(details),
            onTapCancel: () => widget.policy.onSceneTapCancel(),
            onLongPress: () => widget.policy.onSceneLongPress(),
            onLongPressStart: (LongPressStartDetails details) =>
                widget.policy.onSceneLongPressStart(details),
            onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) =>
                widget.policy.onSceneLongPressMoveUpdate(details),
            onLongPressEnd: (LongPressEndDetails details) =>
                widget.policy.onSceneLongPressEnd(details),
            onLongPressUp: () => widget.policy.onSceneLongPressUp(),
          ),
        ),
      ),
    );
  }
}
